import { pageRecords } from '@/interface/pageInterface';
import { valiObj } from '@/validates/validate/validate';
import { BadRequestException } from '@nestjs/common';
import { BaseService } from '../baseService';
import { pageWrapper } from '@/lib/wrapper/pageWrapper';
import { orderWrapper } from '@/lib/wrapper/orderWrapper';
import { fieldWrapper } from '@/lib/wrapper/fieldWrapper';
import { Equal, ILike } from 'typeorm';
import { conditionWrapper } from '@/lib/wrapper/conditionWrapper';

/**
 *
 * 用法:
 *  在service继承并按照nest:在constructor形参中注入entity
 *  并将此enetity在super(entity)
 * 包含基本的增(一条数据)
 *          删(一条或者多条[id,id])
 *          查(一条)
 *          查(带有分页)
 *          改(一条)
 *          状态修改(表中需要有state字段)
 * 复杂查询请自定义：
 *    比如多条排序 建议：先使用
 *    getRepository(User).createQueryBuilder('user')
 */
export class IbaseService<T> implements BaseService<T> {
  entity;
  constructor(entity) {
    /**
     * 可以获取到表名:tableName，字段名 propertiesMap
     * connction:Connection{manager:[EntityManager]}
     * columns:[[ColumnMetadata],[ColumnMetadata]]
     */
    this.entity = entity;
  }

  /**
   * 注意：此方法不会检查SQL错误，有就直接返回，比如设置了唯一索引，结果插入了相同的，有唯一索引的建议使用saveAtferExist
   * 适用于直接插入数据，不需要对比的
   * @param data   要插入的数据:obejct
   * @returns      影响的行数:number
   */
  public async save(data): Promise<number> {
    try {
      if (!valiObj(data)) {
        throw new Error('实体对象错误');
      }
      /**
       * insert - 插入新实体或实体数组。
       */
      let { raw } = await this.entity.insert(data);
      const { affectedRows } = raw;
      return affectedRows;
    } catch (error) {
      if (error.name == 'QueryFailedError') {
        throw new BadRequestException("创建失败");
      }
      throw new BadRequestException(error);
    }
  }

  /**
   *  是否考虑批量更新。待定
   * @param id        要更新的id
   * @param newData   新的数据 :obj
   * @returns  影响的行数
   */
  public async update(id, newData): Promise<T> {
    try {
      let hasOne = await this.getOneById(id);
      if (!hasOne) {
        throw new Error('要更新的数据不存在');
      }
      if (!valiObj(newData)) {
        throw new Error('新数据错误');
      }
      let { affected } = await this.entity.update(id, newData);
      return affected;
    } catch (error) {
      throw new BadRequestException(error);
    }
  }

  /**  根据id 删除一条
   *
   * @param id   参数:id
   */
  public async deleteOneById(id): Promise<T> {
    try {
      let hasOne = await this.getOneById(id);
      if (!hasOne) {
        throw new Error('数据不存在,删除失败');
      }
      /**
       * remove - 删除给定的实体或实体数组。
       * 此方法需要是实体,而不是所以参数是返回的整体对象
       */
      return await this.entity.remove(hasOne);
    } catch (error) {
      throw new BadRequestException(error);
    }
  }
  /**
   * 批量删除多条数据
   * @param ids 根据id组成的数组删除
   */
  public async deleteByIds(ids): Promise<number> {
    try {
      /**
       *  delete -根据实体 id, ids 或给定的条件删除实体
       */
      let { affected } = await this.entity.delete(ids);
      return affected;
    } catch (error) {
      throw new BadRequestException(error);
    }
  }

  /**
   * 设置是否可以用:注意，该方法需要数据库中有state字段
   * @param id
   * @param state     状态值
   */
  public async setStatus(id, state): Promise<T> {
    try {
      let hasOne = await this.getOneById(id);
      if (!hasOne) {
        throw new Error('数据不存在,更新状态失败');
      }
      /**
       * fix: 修复BUG。state是0或者1 JS自动转换类型，
       * 如果状态是0 即：这个记录不可用时,自动抛出不存在字段
       */
      if (!Object.keys(hasOne).includes('state')) {
        // if (hasOne.state == 0) {
        throw new Error('数据库state字段不存在，无法更新');
      }
      return await this.update(id, { state });
    } catch (error) {
      throw new BadRequestException(error);
    }
  }
  /**
   *  根据id 查询一条数据
   * @param id  要查询的id
   * 返回值:any 原因:有些方法需要各自的entity的属性，
   * 目前没想到好方法获取
   */
  public async getOneById(id): Promise<any> {
    try {
      let hasOne = await this.entity.findOne({
        where: { id },
      });
      // console.log('get', hasOne);
      return hasOne;
    } catch (error) {
      throw new BadRequestException(error);
    }
  }

  /**
   * 不建议直接使用，建议在后端在包一层，目的：后端在该方法中传入字段field,注意按照顺序 - - 
   * @param field       可选:根据什么字段查询分页，默认相当于order by [field] [sort]
   * @param pageSize    可选: 一页显示的数量,默认:dev:5,prod:10
   * @param pageNumber  可选: 第几页,默认:dev:1,prod:10
   * @param sort        可选: 排序规则:"ASC","DESC"中的一个
   */
  public async getAllWithPageOrOrder(
    field?,
    sort?,
    pageSize?: number,
    pageNumber?: number,
  ): Promise<pageRecords<T>> {
    try {
      /*
      可以获取到表名:tableName，字段名 propertiesMap
     * connction:Connection{manager:[EntityManager]}
     */
      // 规范pageSize和pageNumber
      // wrapper必须要传注意，传的即使是undefined都可以，而不能是可选
      // 可选参数：如果没有值他会当成是必选的那个参数的值，大坑
      const wrapper = pageWrapper(pageSize, pageNumber);
      // 规范order
      const order = orderWrapper(this.entity, field, sort);
      //查询
      const r = await this.entity.findAndCount({
        order,
        skip: (wrapper.pageNumber - 1) * wrapper.pageSize,
        take: wrapper.pageSize,
      });

      const records = r[0];
      const total = r[1];
      return { total, records };
    } catch (error) {
      throw new BadRequestException(error);
    }
  }

  /**
   * 不建议直接使用，建议在后端在包一层，目的：后端在该方法中传入字段field
   *  根据字段查找满足条件的数据，并可以根据该字段设置排序
   * @param field       example:role_id
   * @param value       查询的值
   * @param pageSize    可选: 一页显示的数量,默认:dev:5,prod:10
   * @param pageNumber  可选: 第几页,默认:dev:1,prod:10
   * @param sort        可选: 排序规则:"ASC","DESC"中的一个
   * @param condition   可选: 查询条件:like = > < 中的一个
   */
  public async findManyByField(
    field: string,
    value: string,
    pageSize?: number,
    pageNumber?: number,
    sort?: string,
    condition?: string,
  ): Promise<pageRecords<T>> {
    try {
      const wrapper = pageWrapper(pageSize, pageNumber);
      const order = orderWrapper(this.entity, field, sort);
      field = fieldWrapper(this.entity, field);
      condition = conditionWrapper(value, condition);
      let r = await this.entity.findAndCount({
        // Ilike:找到第一个就不找了
        where: { [field]: condition },
        order,
        skip: (wrapper.pageNumber - 1) * wrapper.pageSize,
        take: wrapper.pageSize,
        /**
         * limit: 一页限制几条
         * offset：偏移量
         */
      });
      const records = r[0];
      const total = r[1];
      return { total, records };
    } catch (error) {
      throw new BadRequestException(error);
    }
  }

  /**
   * 根据字段like查询 根据结果采用update_time desc 排序
   * 有一个缺点，这样好像容易暴露数据库字段名称。。。 看来还是的用ES那种？
   * @param field       字段名称
   * @param value       根据什么模糊查询
   * @param pageSize    可选: 一页显示的数量,默认:dev:5,prod:10
   * @param pageNumber  可选: 第几页,默认:dev:1,prod:10
   */
  public async search(
    field: string,
    value: string,
    pageSize?: number,
    pageNumber?: number,
  ): Promise<pageRecords<T>> {
    try {
      const wrapper = pageWrapper(pageSize, pageNumber);

      field = fieldWrapper(this.entity, field);

      let r = await this.entity.findAndCount({
        where: { [field]: ILike('%' + value + '%') },
        order: { update_time: 'DESC' },
        skip: (wrapper.pageNumber - 1) * wrapper.pageSize,
        take: wrapper.pageSize,
      });
      const records = r[0];
      const total = r[1];
      return { total, records };
    } catch (error) {
      throw new BadRequestException(error);
    }
  }

  /**
   * 在插入之前根据字段和值 查看是否已经存在，如果不存在才插入,比如用户是唯一的，那么应该先比对，在插入
   * @param t      插入的数据
   * @param field  必须和value配合，否则不生效
   * @param value  必须和field配合，否则不生效
   */
  public async saveAtferExist(field, value, t): Promise<number | boolean> {
    try {
      if (field && value) {
        // 判断字段是否存在
        field = fieldWrapper(this.entity, field);
        // 判断该字段中是否有值和插入的一样
        const isExist = await this.entity.findOne({
          where: {
            [field]: Equal(value),
          },
        });
        if (isExist) {
          // 如果存在，则抛出异常，并不插入
          throw new BadRequestException(`字段:${field}的值:${value} 已存在`);
        } else {
          // 如果不存在，但是查询的值和要插入的该字段的值不一样，报错，并不插入
          if (t[field] !== value) {
            throw new BadRequestException(
              `字段:${field}的值:${value} 和要插入 ${t[field]} 的不一样`,
            );
          }
          // 满足条件后在插入
          return await this.save(t);
        }
      }  
    } catch (error) {
      // console.log(error);
      throw new BadRequestException(error);
    }
  }
}
